/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2005.
-------------------------------------------------------------------------
$Id$
$DateTime$
Description: Implements a light part

-------------------------------------------------------------------------
History:
- 24:10:2005: Created by Mathieu Pinard

*************************************************************************/
#include "StdAfx.h"

#include "ICryAnimation.h"
#include "IVehicleSystem.h"

#include "CryAction.h"
#include "Vehicle.h"
#include "VehicleComponent.h"
#include "VehiclePartBase.h"
#include "VehiclePartLight.h"


#define DEFAULT_FLOAT -1.f
#define DEFAULT_INT -1
#define DEFAULT_VEC3 Vec3(0)

BEGIN_SHARED_PARAMS(SVehicleLightParams)

	SVehicleLightParams() : 
		radius(5.0f), 
		diffuseMult(1.0f), 
		diffuseMult_fp(1.0f), 
		specularMult(1.0f), 
		coronaDistIntensity(1.0f), 
		coronaDistSize(1.0f), 
		coronaScale(1.0f), 
		frustumAngle(45.0f), 
		HDRDynamic(1.0f), 
		animSpeed(1.0f),
		diffuse(1.0f, 1.0f, 1.0f), 
		specular(1.0f, 1.0f, 1.0f),
		style(0),
		animPhase(0),
		castShadows(false), 
		fakeLight(false),
		automatic(false),
		timeOffFrom(0.f),
		timeOffTo(0.f),
		viewDistRatio(100)
	{
	}

	string	texture, material;

	float	radius, diffuseMult, diffuseMult_fp, specularMult, coronaDistIntensity, coronaDistSize, coronaScale, frustumAngle, HDRDynamic, animSpeed;

	Vec3	diffuse, specular;

	int		style;
	int		animPhase;
	bool	castShadows;
	bool fakeLight;

	bool automatic;
	float timeOffFrom;
	float timeOffTo;

	int viewDistRatio;

END_SHARED_PARAMS

DEFINE_SHARED_PARAMS_TYPE_INFO(SVehicleLightParams)

//------------------------------------------------------------------------
CVehiclePartLight::CVehiclePartLight() 
{  
  m_pMaterial = 0;
  m_pHelper = 0;

  m_diffuseMult[0] = m_diffuseMult[1] = 1.f;
  m_diffuseCol.Set(1,1,1);
  m_enabled = false;

	m_automatic = false;
	m_timeOffFrom = 0.f;
	m_timeOffTo = 0.f;

	m_viewDistRatio = 100;
}

//------------------------------------------------------------------------
CVehiclePartLight::~CVehiclePartLight()
{
  ToggleLight(false);
}

//------------------------------------------------------------------------
bool CVehiclePartLight::Init(IVehicle* pVehicle, const CVehicleParams& table, IVehiclePart* parent, CVehicle::SPartInitInfo& initInfo, int partType)
{
		if (!CVehiclePartBase::Init(pVehicle, table, parent, initInfo, eVPT_Light))
				return false;

		Vec3 specularCol(1.f, 1.f, 1.f);
		float specularMul = 1.0f;

		m_light.m_nLightStyle = 0;
		m_light.SetPosition( Vec3(ZERO) );
		m_light.m_fRadius = 5.0f;  
		m_light.m_fCoronaScale = 1.0f;
		m_light.m_fCoronaDistIntensityFactor = 1.0f;
		m_light.m_fCoronaDistSizeFactor = 1.0f;

		m_light.m_Flags |= DLF_LIGHTSOURCE;  
		m_light.m_Flags |= DLF_DEFERRED_LIGHT;
		m_light.m_Flags |= DLF_THIS_AREA_ONLY;
		m_light.m_Flags &= ~DLF_DISABLED; 

		if(CVehicleParams lightTable = table.findChild("Light"))
		{
				m_lightType = lightTable.getAttr("type");

				ISharedParamsManager	*pSharedParamsManager = CCryAction::GetCryAction()->GetISharedParamsManager();

				CRY_ASSERT(pSharedParamsManager);

				SVehicleLightParamsConstPtr	pVehicleLightParams = CastSharedParamsPtr<SVehicleLightParams>(pSharedParamsManager->Get(m_lightType));

				if(!pVehicleLightParams)
				{
						XmlNodeRef	lightsRootNode = gEnv->pSystem->LoadXmlFromFile("Scripts/Entities/Vehicles/Lights/DefaultVehicleLights.xml");

						if(lightsRootNode)
						{
								if(XmlNodeRef lightsListNode = lightsRootNode->findChild("Lights"))
								{
										int	numberOfLights = lightsListNode->getChildCount();

										for(int i = 0; i < numberOfLights; ++ i)
										{
												if(XmlNodeRef lightParamsNode = lightsListNode->getChild(i))
												{
														if(!strcmp(lightParamsNode->getTag(), "Light"))
														{
																string	type = lightParamsNode->getAttr("type");

																if(!pSharedParamsManager->Get(type))
																{
																		SVehicleLightParams	lightParams;

																		lightParams.texture		= lightParamsNode->getAttr("texture");
																		lightParams.material	= lightParamsNode->getAttr("material");

																		lightParamsNode->getAttr("radius", lightParams.radius);

																		lightParamsNode->getAttr("diffuseMult", lightParams.diffuseMult);

																		if(!lightParamsNode->getAttr("diffuseMult_fp", lightParams.diffuseMult_fp))
																		{
																				lightParams.diffuseMult_fp = lightParams.diffuseMult;
																		}

																		lightParamsNode->getAttr("specularMult", lightParams.specularMult);

																		lightParamsNode->getAttr("coronaDistIntensity", lightParams.coronaDistIntensity);

																		lightParamsNode->getAttr("coronaDistSize", lightParams.coronaDistSize);

																		lightParamsNode->getAttr("coronaScale", lightParams.coronaScale);

																		lightParamsNode->getAttr("frustumAngle", lightParams.frustumAngle);

																		lightParamsNode->getAttr("diffuse", lightParams.diffuse);

																		lightParamsNode->getAttr("HDRDynamic", lightParams.HDRDynamic);

																		lightParamsNode->getAttr("specular", lightParams.specular);

																		lightParamsNode->getAttr("style", lightParams.style);

																		lightParamsNode->getAttr("castShadows", lightParams.castShadows);

																		lightParamsNode->getAttr("fakeLight", lightParams.fakeLight);

																		lightParamsNode->getAttr("animSpeed", lightParams.animSpeed);

																		lightParamsNode->getAttr("animPhase", lightParams.animPhase);

																		if (lightParamsNode->haveAttr("timeOffFrom"))
																		{
																			lightParams.automatic = true;
																			lightParamsNode->getAttr("timeOffFrom", lightParams.timeOffFrom);
																		}
																		if (lightParamsNode->haveAttr("timeOffTo"))
																		{
																			lightParams.automatic = true;
																			lightParamsNode->getAttr("timeOffTo", lightParams.timeOffTo);
																		}

																		lightParamsNode->getAttr("viewDistRatio", lightParams.viewDistRatio);

																		SVehicleLightParamsConstPtr	pNewVehicleLightParams = CastSharedParamsPtr<SVehicleLightParams>(pSharedParamsManager->Register(type, lightParams));

																		if(type == m_lightType)
																		{
																				pVehicleLightParams = pNewVehicleLightParams;
																		}
																}
														}
												}
										}
								}
						}
				}

				if(pVehicleLightParams)
				{
						m_light.m_fRadius						= pVehicleLightParams->radius;
						m_diffuseCol							= pVehicleLightParams->diffuse;
						m_diffuseMult[1]						= pVehicleLightParams->diffuseMult;
						m_diffuseMult[0]						= pVehicleLightParams->diffuseMult_fp;
						specularCol								= pVehicleLightParams->specular;
						specularMul								= pVehicleLightParams->specularMult;
						m_light.m_fCoronaDistIntensityFactor	= pVehicleLightParams->coronaDistIntensity;
						m_light.m_fCoronaDistSizeFactor			= pVehicleLightParams->coronaDistSize;
						m_light.m_fCoronaScale					= pVehicleLightParams->coronaScale;
						m_light.m_nLightStyle					= pVehicleLightParams->style;
						m_light.m_fLightFrustumAngle			= pVehicleLightParams->frustumAngle;
						m_light.m_fHDRDynamic					= pVehicleLightParams->HDRDynamic;
						m_light.m_fAnimSpeed					= pVehicleLightParams->animSpeed;
						m_light.m_nLightPhase					= pVehicleLightParams->animPhase;

						if(pVehicleLightParams->castShadows)
						{
								m_light.m_Flags |= DLF_CASTSHADOW_MAPS;
								m_light.m_Flags &= ~DLF_HASCLIPBOUND;
						}

						m_light.m_Flags = pVehicleLightParams->fakeLight ? m_light.m_Flags|DLF_FAKE : m_light.m_Flags&~DLF_FAKE;
						
						if (pVehicleLightParams->automatic)
						{
							m_automatic = true;
							m_timeOffFrom = pVehicleLightParams->timeOffFrom;
							m_timeOffTo = pVehicleLightParams->timeOffTo;
						}

						m_viewDistRatio = pVehicleLightParams->viewDistRatio;

						if(!pVehicleLightParams->material.empty())
								m_pMaterial = gEnv->p3DEngine->GetMaterialManager()->LoadMaterial(pVehicleLightParams->material.c_str());

						if (!pVehicleLightParams->texture.empty())
								m_light.m_pLightImage = gEnv->pRenderer->EF_LoadTexture(pVehicleLightParams->texture.c_str());

						if (m_light.m_fLightFrustumAngle && m_light.m_pLightImage && m_light.m_pLightImage->IsTextureLoaded())
						{
								m_light.m_Flags |= DLF_PROJECT;
						}
						else
						{
								if (m_light.m_pLightImage)
								{
										CryWarning(VALIDATOR_MODULE_GAME, VALIDATOR_WARNING, "Unable to load light texture: %s for vehicle: %s. Using point light instead.", pVehicleLightParams->texture.c_str(), m_pVehicle->GetEntity()->GetName());

										m_light.m_pLightImage->Release();
										m_light.m_pLightImage = 0;
								}
								m_light.m_Flags |= DLF_POINT;
						}
				}
		}

		m_light.SetLightColor(ColorF(m_diffuseCol*m_diffuseMult[1], 1.f));
		m_light.SetSpecularMult( specularMul / m_diffuseMult[1] );

		return true;
}


//------------------------------------------------------------------------
void CVehiclePartLight::PostInit()
{
  m_pHelper = m_pVehicle->GetHelper(m_pSharedParameters->m_helperPosName.c_str());

  // get Components this Part belongs to. 
  // currently that's only needed for Lights. 
  for (int i=0,nComps=m_pVehicle->GetComponentCount(); i<nComps; ++i)
  {    
    IVehicleComponent* pComponent = m_pVehicle->GetComponent(i);      
    
    for (int j=0,nParts=pComponent->GetPartCount(); j<nParts; ++j)
    {
      if (pComponent->GetPart(j) == this)
      {
        m_components.push_back(pComponent);
        break;
      }
    }
  }
  
  if (VehicleCVars().v_lights_enable_always)
    ToggleLight(true);
}

//------------------------------------------------------------------------
void CVehiclePartLight::Reset()
{
	CVehiclePartBase::Reset();
	
  ToggleLight(false);  
}


//------------------------------------------------------------------------
void CVehiclePartLight::OnEvent(const SVehiclePartEvent& event)
{
  switch (event.type)
	{
	case eVPE_Damaged:
    if (event.fparam >= 1.0f)
    { 
      float dmg = 1.f / max(1.f,(float)m_components.size());
      m_damageRatio += dmg;
		  
      if (m_damageRatio >= 1.f && IsEnabled())
			  ToggleLight(false);
    }
		break;

	case eVPE_DriverEntered:
		if (m_automatic)
		{
			ToggleLight(true);
		}
		break;

	case eVPE_DriverLeft:
		if (m_automatic)
		{
			ToggleLight(false);
		}
		break;

	default:
	  CVehiclePartBase::OnEvent(event);
	}
}

//------------------------------------------------------------------------
void CVehiclePartLight::ToggleLight(bool enable)
{
	if (enable && !IsEnabled())
	{
		// 0: no lights at all (only intended for debugging etc.)
		// 1: lights only enabled for the local player
		// 2: all lights enabled
		if (VehicleCVars().v_lights == 0
			|| (VehicleCVars().v_lights == 1 && !m_pVehicle->IsPlayerPassenger()))
			return;

		if (m_pHelper && m_damageRatio < 1.0f)
		{
			IEntity* pEntity = GetEntity();

			m_slot = pEntity->LoadLight(m_slot, &m_light);
		
			if (m_slot != -1 && m_pMaterial)      
				pEntity->SetSlotMaterial(m_slot, m_pMaterial);

			UpdateLight(0.f);

			if (IEntityRenderProxy* pRenderProxy = static_cast<IEntityRenderProxy*>(pEntity->GetProxy(ENTITY_PROXY_RENDER)))
			{
				if (IRenderNode* pRenderNode = pRenderProxy->GetRenderNode())
				{
					pRenderNode->SetViewDistRatio(m_viewDistRatio);
				}
			}
		}

		m_pVehicle->SetObjectUpdate(this, m_automatic ? IVehicle::eVOU_AlwaysUpdate : IVehicle::eVOU_Visible);
		m_enabled = true;
	}
	else if (!enable && IsEnabled())
	{ 
		if (m_slot != -1)
		{
			GetEntity()->FreeSlot(m_slot);
			m_slot = -1;
		}  

		m_pVehicle->SetObjectUpdate(this, m_automatic ? IVehicle::eVOU_AlwaysUpdate : IVehicle::eVOU_NoUpdate);
		m_enabled = false;
	}

}

//-----------------------------------------------------------------------
void CVehiclePartLight::Update(const float frameTime)
{
	if (m_automatic)
	{
		float hours = gEnv->p3DEngine->GetTimeOfDay()->GetTime();
		if ((m_timeOffFrom <= hours) && (hours <= m_timeOffTo))
		{
			if (IsEnabled())
			{
				ToggleLight(false);
			}
		}
		else
		{
			if (!IsEnabled())
			{
				ToggleLight(true);
			}
		}
	}
	
	if (IsEnabled())  
    UpdateLight(frameTime);
}

//-----------------------------------------------------------------------
void CVehiclePartLight::UpdateLight(const float frameTime)
{ 
  if (m_slot == -1)
    return;

	// move to vehicle event change view?
  if (m_diffuseMult[0] != m_diffuseMult[1])
  {
    SEntitySlotInfo info;
    if (m_pVehicle->GetEntity()->GetSlotInfo(m_slot, info) && info.pLight)
    {
      CDLight& light = info.pLight->GetLightProperties();    

      IActor* pActor = CCryAction::GetCryAction()->GetClientActor();
      bool localPlayer = (pActor != NULL) && (pActor->GetLinkedVehicle() == m_pVehicle);

			IVehicleSeat* pSeat = pActor ? m_pVehicle->GetSeatForPassenger(pActor->GetEntityId()) : NULL;
			IVehicleView* pView = pSeat? pSeat->GetView(pSeat->GetCurrentView()) : NULL;
			bool isThirdPersonView = pView? pView->IsThirdPerson() : true;
      if (localPlayer && !isThirdPersonView)
				light.SetLightColor(ColorF(m_diffuseCol * m_diffuseMult[0], 1.f));
      else
				light.SetLightColor(ColorF(m_diffuseCol * m_diffuseMult[1], 1.f));    
    }
  }  

  if (m_pHelper)
  { 
    const static Matrix33 rot(Matrix33::CreateRotationXYZ(Ang3(0.f, 0.f, DEG2RAD(90.f))));
    
    Matrix34 helperTM;
		m_pHelper->GetVehicleTM(helperTM);
    Matrix34 localTM = Matrix33(helperTM) * rot;
    localTM.SetTranslation(helperTM.GetTranslation());

    GetEntity()->SetSlotLocalTM(m_slot, localTM);  
  }

}

//------------------------------------------------------------------------
void CVehiclePartLight::Serialize(TSerialize ser, EEntityAspects aspects)
{
  CVehiclePartBase::Serialize(ser, aspects);

	if (ser.GetSerializationTarget()!=eST_Network)
	{
		bool isEnabled = IsEnabled();
		ser.Value("lightEnabled", isEnabled, 'bool');

		if (ser.IsReading())
		{	
			ToggleLight(isEnabled);
		}
	}
}

DEFINE_VEHICLEOBJECT(CVehiclePartLight);
